The Master Parent Class: System.Object

To wrap up this chapter, I’d like to examine the details of the master parent class in the .NET platform: Object. As you were reading the previous section, you may have noticed that the base classes in your hierarchies (Car, Shape, Employee) never explicitly specify their parent classes:

// Who is the parent of Car?
class Car
{...}

In the .NET universe, every type ultimately derives from a base class named System.Object (which can be represented by the C# object keyword [lowercase ‘o’]). The Object class defines a set of common members for every type in the framework. In fact, when you do build a class that does not explicitly define its parent, the compiler automatically derives your type from Object. If you want to be very clear in your intentions, you are free to define classes that derive from Object as follows:

// Here we are explicitly deriving from System.Object.
class Car : object
{...}

Like any class, System.Object defines a set of members. In the following formal C# definition, note that some of these items are declared virtual, which specifies that a given member may be overridden by a subclass, while others are marked with static (and are therefore called at the class level):

public class Object
{
    // Virtual members.
    public virtual bool Equals(object obj);
    protected virtual void Finalize();
    public virtual int GetHashCode();
    public virtual string ToString();

    // Instance level, non-virtual members.
    public Type GetType();
    protected object MemberwiseClone();

    // Static members.
    public static bool Equals(object objA, object objB);
    public static bool ReferenceEquals(object objA, object objB);
}

Table 6-1 offers a rundown of the functionality provided by some of the methods you’re most likely to make use of.

Table 6-1. Core Members of System.Object

Instance Method of Object Class Meaning in Life
Equals() By default, this method returns true only if the items being compared refer to the exact same item in memory. Thus, Equals() is used to compare object references, not the state of the object. Typically, this method is overridden to return true only if the objects being compared have the same internal state values (that is, value-based semantics). Be aware that if you override Equals(), you should also override GetHashCode(), as these methods are used internally by Hashtable types to retrieve subobjects from the container. The ValueType class overrides this method for all structures, so they work with value-based comparisons.
Finalize() For the time being, you can understand this method (when overridden) is called to free any allocated resources before the object is destroyed.
GetHashCode() This method returns an int that identifies a specific object instance.
ToString() This method returns a string representation of this object, using the <namespace>.<type name> format (termed the fully qualified name). This method will often be overridden by a subclass to return a tokenized string of name/value pairs that represent the object’s internal state, rather than its fully qualified name.
GetType() This method returns a Type object that fully describes the object you are currently referencing. In short, this is a Runtime Type Identification (RTTI) method available to all objects.
MemberwiseClone() This method exists to return a member-by-member copy of the current object, which is often used when cloning an object.

To illustrate some of the default behavior provided by the Object base class, create a new C# Console Application named ObjectOverrides. Insert a new C# class type that contains the following empty class definition for a type named Person:

// Remember! Person extends Object.
class Person {}

Now, update your Main() method to interact with the inherited members of System.Object as follows:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("***** Fun with System.Object *****\n");
        Person p1 = new Person();

        // Use inherited members of System.Object.
        Console.WriteLine("ToString: {0}", p1.ToString());
        Console.WriteLine("Hash code: {0}", p1.GetHashCode());
        Console.WriteLine("Type: {0}", p1.GetType());

        // Make some other references to p1.
        Person p2 = p1;
        object o = p2;

        // Are the references pointing to the same object in memory?
        if (o.Equals(p1) && p2.Equals(o))
        {
            Console.WriteLine("Same instance!");
        }
        Console.ReadLine();
    }
}

Here is the output of the current Main() method.

***** Fun with System.Object *****

ToString: ObjectOverrides.Person
Hash code: 46104728
Type: ObjectOverrides.Person
Same instance!

First, notice how the default implementation of ToString() returns the fully qualified name of the current type (ObjectOverrides.Person). Every C# project defines a “root namespace,” which has the same name of the project itself. Here, you created a project named ObjectOverrides; thus the Person type (as well as the Program class) have both been placed within the ObjectOverrides namespace.

The default behavior of Equals() is to test whether two variables are pointing to the same object in memory. Here, you create a new Person variable named p1. At this point, a new Person object is placed on the managed heap. p2 is also of type Person. However, you are not creating a new instance, but rather assigning this variable to reference p1. Therefore, p1 and p2 are both pointing to the same object in memory, as is the variable o (of type object, which was thrown in for good measure). Given that p1, p2, and o all point to the same memory location, the equality test succeeds.

Although the canned behavior of System.Object can fit the bill in a number of cases, it is quite common for your custom types to override some of these inherited methods. To illustrate, update the Person class to support some properties representing an individual’s first name, last name, and age, each of which can be set by a custom constructor:

// Remember! Person extends Object.
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public Person(string fName, string lName, int personAge)
    {
        FirstName = fName;
        LastName = lName;
        Age = personAge;
    }

    public Person(){}
}

Overriding System.Object.ToString()

Many classes (and structures) that you create can benefit from overriding ToString() in order to return a string textual representation of the type’s current state. This can be quite helpful for purposes of debugging (among other reasons). How you choose to construct this string is a matter of personal choice; however, a recommended approach is to separate each name/value pair with semicolons and wrap the entire string within square brackets (many types in the .NET base class libraries follow this approach). Consider the following overridden ToString() for your Person class:

public override string ToString()
{
    string myState;
    myState = string.Format("[First Name: {0}; Last Name: {1}; Age: {2}]",
        FirstName, LastName, Age);
    return myState;
}

This implementation of ToString() is quite straightforward, given that the Person class only has three pieces of state data. However, always remember that a proper ToString() override should also account for any data defined up the chain of inheritance.

When you override ToString() for a class extending a custom base class, the first order of business is to obtain the ToString() value from your parent using the base keyword. Once you have obtained your parent’s string data, you can append the derived class’s custom information.

Overriding System.Object.Equals()

Let’s also override the behavior of Object.Equals() to work with value-based semantics. Recall that by default, Equals() returns true only if the two objects being compared reference the same object instance in memory. For the Person class, it may be helpful to implement Equals() to return true if the two variables being compared contain the same state values (e.g., first name, last name, and age).

First of all, notice that the incoming argument of the Equals() method is a general System. Object. Given this, your first order of business is to ensure the caller has indeed passed in a Person object, and as an extra safeguard, to make sure the incoming parameter is not a null reference.

Once you have established the caller has passed you an allocated Person, one approach to implement Equals() is to perform a field-by-field comparison against the data of the incoming object to the data of the current object:

public override bool Equals(object obj)
{
    if (obj is Person && obj != null)
    {
        Person temp;
        temp = (Person)obj;
        if (temp.FirstName == this.FirstName
            && temp.LastName == this.LastName
            && temp.Age == this.Age)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    return false;
}

Here, you are examining the values of the incoming object against the values of your internal values (note the use of the this keyword). If the name and age of each are identical, you have two objects with the exact same state data and therefore return true. Any other possibility results in returning false.

While this approach does indeed work, you can certainly imagine how labor intensive it would be to implement a custom Equals() method for nontrivial types that may contain dozens of data fields. One common shortcut is to leverage your own implementation of ToString(). If a class has a prim-andproper implementation of ToString() that accounts for all field data up the chain of inheritance, you can simply perform a comparison of the object’s string data:

public override bool Equals(object obj)
{
    // No need to cast 'obj' to a Person anymore,
    // as everything has a ToString() method.
    return obj.ToString() == this.ToString();
}

Notice in this case that we no longer need to check if the incoming argument is of the correct type (a Person in our example), as everything in .NET supports a ToString() method. Even better, we no longer need to perform a property-by-property equality check, as we are now simply testing the value returned from ToString().

Overriding System.Object.GetHashCode()

When a class overrides the Equals() method, you should also override the default implementation of GetHashCode(). Simply put, a hash code is a numerical value that represents an object as a particular state. For example, if you create two string variables that hold the value Hello, you would obtain the same hash code. However, if one of the string objects were in all lowercase (hello), you would obtain different hash codes.

By default, System.Object.GetHashCode() uses your object’s current location in memory to yield the hash value. However, if you are building a custom type that you intend to store in a Hashtable type (within the System.Collections namespace), you should always override this member, as the Hashtable will be internally invoking Equals() and GetHashCode() to retrieve the correct object.

Note To be more specific, the System.Collections.Hashtable class calls GetHashCode() internally to gain an general idea where the object is located, but a subsequent (internal) call to Equals() determines the exact match.

Although you are not going to place your Person into a System.Collections.Hashtable, for completion, let’s override GetHashCode(). There are many algorithms that can be used to create a hash code, some fancy, others not so fancy. Most of the time, you are able to generate a hash code value by leveraging the System.String’s GetHashCode() implementation.

Given that the String class already has a solid hash code algorithm that is using the character data of the String to compute a hash value, if you can identify a piece of field data on your class that should be unique for all instances (such as a Social Security number), simply call GetHashCode() on that point of field data. Thus, if the Person class defined a SSN property, we could author the following code:

// Return a hash code based on a point of unique string data.
public override int GetHashCode()
{
    return SSN.GetHashCode();
}

If you cannot find a single point of unique string data, but you have overridden ToString(), call GetHashCode() on your own string representation:

// Return a hash code based on the person's ToString() value.
public override int GetHashCode()
{
    return this.ToString().GetHashCode();
}

Testing Your Modified Person Class

Now that you have overridden the virtual members of Object, update Main() to test your updates.

static void Main(string[] args)
{
    Console.WriteLine("***** Fun with System.Object *****\n");
    // NOTE: We want these to be identical to test
    // the Equals() and GetHashCode() methods.
    Person p1 = new Person("Homer", "Simpson", 50);
    Person p2 = new Person("Homer", "Simpson", 50);

    // Get stringified version of objects.
    Console.WriteLine("p1.ToString() = {0}", p1.ToString());
    Console.WriteLine("p2.ToString() = {0}", p2.ToString());

    // Test Overridden Equals()
    Console.WriteLine("p1 = p2?: {0}", p1.Equals(p2));

    // Test hash codes.
    Console.WriteLine("Same hash codes?: {0}", p1.GetHashCode() == p2.GetHashCode());
    Console.WriteLine();

    // Change age of p2 and test again.
    p2.Age = 45;
    Console.WriteLine("p1.ToString() = {0}", p1.ToString());
    Console.WriteLine("p2.ToString() = {0}", p2.ToString());
    Console.WriteLine("p1 = p2?: {0}", p1.Equals(p2));
    Console.WriteLine("Same hash codes?: {0}", p1.GetHashCode() == p2.GetHashCode());
    Console.ReadLine();
}

The output can be seen here:

***** Fun with System.Object *****

p1.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50]
p2.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50]
p1 = p2?: True
Same hash codes?: True

p1.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50]
p2.ToString() = [First Name: Homer; Last Name: Simpson; Age: 45]
p1 = p2?: False
Same hash codes?: False

The Static Members of System.Object

In addition to the instance-level members you have just examined, System.Object does define two (very helpful) static members that also test for value-based or reference-based equality. Consider the following code:

static void StaticMembersOfObject()
{
    // Static members of System.Object.
    Person p3 = new Person("Sally", "Jones", 4);
    Person p4 = new Person("Sally", "Jones", 4);
    Console.WriteLine("P3 and P4 have same state: {0}", object.Equals(p3, p4));
    Console.WriteLine("P3 and P4 are pointing to same object: {0}",
        object.ReferenceEquals(p3, p4));
}

Here, you are able to simply send in two objects (of any type) and allow the System.Object class to determine the details automatically. These methods can be very helpful when you have redefined equality for a custom type, yet still need to quickly determine whether two reference variables point to the same location in memory (via the static ReferenceEquals() method).

Source Code The ObjectOverrides project is located under the Chapter 6 subdirectory.